import Data.Char (chr, isLower, ord)

-- 第一个函数可以将一个字符转换成 ASCII 码
-- 另外一个函数则是第一个函数的反函数，可以将 ASCII 转换成字符
char2int :: Char -> Int
char2int c = ord c - ord 'a'

int2char :: Int -> Char
int2char n = chr (ord 'a' + n)

-- 将字符向右移动 n 位
shift :: Int -> Char -> Char
shift n c
  | isLower c = int2char ((char2int c + n) `mod` 26)
  | otherwise = c

-- 加密
encode :: Int -> String -> String
encode n xs = [shift n x | x <- xs]

-- 解密
decode n = encode (-n)

-- 加密测试
tea = encode 13 "see you this afternoon"

-- 解密
teb = decode 13 tea

-- ----- 根据字母频率解密
chisqr :: [Float] -> [Float] -> Float
chisqr os es = sum [((o - e) ^ 2) / e | (o, e) <- zip os es]

table :: [Float]
table =
  [ 8.2,
    1.5,
    2.8,
    4.3,
    12.7,
    2.2,
    2.0,
    6.1,
    7.0,
    0.2,
    0.8,
    4.0,
    2.4,
    6.7,
    7.5,
    1.9,
    0.1,
    6.0,
    6.3,
    9.1,
    2.8,
    1.0,
    2.4,
    0.2,
    2.0,
    0.1
  ]

-- 某一英文字符在文本出现的次数
count :: Char -> String -> Int
count x xs = length [x' | x' <- xs, x == x']

-- 求百分比函数
percent :: Int -> Int -> Float
percent n m = (fromIntegral n / fromIntegral m) * 100

-- 文本中有多少个小写英文字母。使用列表内包很容易定义这个函数
lowers :: String -> Int
lowers xs = length [x | x <- xs, isLower x]

-- 查每个字母在文中有多个就可以了
freqs :: String -> [Float]
freqs xs = [percent (count x xs) n | x <- ['a' .. 'z']]
  where
    n = lowers xs

-- rotate 函数来移动列表中的元素所在的位置：
rotate :: Int -> [a] -> [a]
rotate n xs = drop n xs ++ take n xs

-- 得到密文中各个英文字符的频率，记为 table'，
-- table' 与原文频率表table 依次应用 chisqr，这样就得到了所有的距离，
-- 从这些距离中找到最小的，其所在的位置就是解密需要移动的次数。
crack :: String -> String
crack xs = encode (-factor) xs
  where
    factor = head (positions (minimum chitab) chitab)
    chitab = [chisqr (rotate n table') table | n <- [0 .. 25]]
    table' = freqs xs

positions :: (Eq a) => a -> [a] -> [Int]
positions x xs = [i | (x', i) <- zip xs [0 ..], x == x']

tec = crack "frr lbh guvf nsgreabba"

ted = crack "bhe jbeyq vf znxr hc bs ngbzf"